import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;

import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.html.DomElement;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlInput;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlPasswordInput;
import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;
import com.gargoylesoftware.htmlunit.html.HtmlTextInput;


public final class InputDiscovery {
	
	private InputDiscovery() {
	}

	public static void discoverPages(HtmlPage currentPage) throws Exception {
		
		parseQueryStrings(currentPage.getUrl().toString());
		
		List<HtmlAnchor> childPages = currentPage.getAnchors(); 
		for (HtmlAnchor anchor : childPages) {
			if (anchor.getHrefAttribute().equals("#") == false) {
				
				SecurityFuzzer.requestWait();
				
				HtmlPage p = anchor.click();
				String pUrl = p.getUrl().toString();
				if (SecurityFuzzer.discoverPageSet.contains(pUrl) == false) {
					SecurityFuzzer.discoverPageSet.add(pUrl);
					SecurityLoggers.pageDiscoveryLogger.log(Level.INFO, String.format("Page discovered: [%s]", pUrl));
					discoverPages(p);
				}
			}
		}
		
	}
	
	public static void guessPages() {
		
		// don't have the web client print error messages since
		// we are guessing web pages
		SecurityFuzzer.webClient.setPrintContentOnFailingStatusCode(false);
		
		for (String commonPageName : Constants.commonPageNames) {
			for (String commonPageExtension : Constants.commonPageExtensions) {
				String pageValue = SecurityFuzzer.startingPage + commonPageName + commonPageExtension;
				Page guessedPage = null;
				
				SecurityFuzzer.requestWait();
				
				try {
					guessedPage = SecurityFuzzer.webClient.getPage(pageValue);
				} catch (FailingHttpStatusCodeException e) {
				} catch (MalformedURLException e) {
				} catch (IOException e) {
				} catch (Exception e) {
					// ignore failure to load web page; we are only guessing
				}
				
				if (guessedPage != null) {
					parseQueryStrings(pageValue);
					
					SecurityFuzzer.discoverPageSet.add(pageValue);
					SecurityLoggers.pageGuessingLogger.log(Level.INFO, String.format("Page guessed: [%s]", pageValue));
				}
			}
		}
		
		// turn on the web client's error logging for failure to load
		// web pages
		SecurityFuzzer.webClient.setPrintContentOnFailingStatusCode(true);
		
	}
	
	public static void findFormInputs(String currentPageURL) throws Exception {
		
		HtmlPage p = SecurityFuzzer.webClient.getPage(currentPageURL);
		for (HtmlForm form : Completeness.getFormCompletenessSet(p)) {
			SecurityLoggers.inputDiscoveryLogger.log(Level.INFO, 
					String.format("Form discovered on page (%s)", currentPageURL));
			if (form.getByXPath("//input[@type='password']").size() > 0) {
				tryAuthentication(form);
			} else {
				final Set<HtmlInput> inputs = Completeness.getInputCompletenessSet(form);
				final List<HtmlSubmitInput> submits = (List<HtmlSubmitInput>) form.getByXPath("//input[@type='submit']");
				final HtmlSubmitInput button = submits.get(0);
				 
				if (!inputs.isEmpty()) {
					for (HtmlInput i : inputs) {
						SecurityLoggers.inputDiscoveryLogger.log(Level.INFO, 
								String.format("Input field discovered on page (%s): [id=%s, name=%s, type=%s]", 
										currentPageURL, i.getId(), i.getNameAttribute(), i.getTypeAttribute()));
					}
				}
				
				FuzzInput.fuzzTextInputs(inputs, button, form);
			}
		}
		
	}

	private static void tryAuthentication(HtmlForm form) {
		
		List<HtmlPasswordInput> passwords = (List<HtmlPasswordInput>) form.getByXPath("//input[@type='password']");
		List<HtmlSubmitInput> submits = (List<HtmlSubmitInput>) form.getByXPath("//input[@type='submit']");
		final HtmlSubmitInput submitButton = submits.get(0);
		List<HtmlTextInput> texts = (List<HtmlTextInput>) form.getByXPath("//input[@type='text']");
		
		if (!passwords.isEmpty()) {
			for (HtmlPasswordInput i : passwords) {
				SecurityLoggers.inputDiscoveryLogger.log(Level.INFO, 
						String.format("Password field discovered on page (%s): [id=%s, name=%s, type=%s]", 
								form.getPage().getUrl(), i.getId(), i.getNameAttribute(), i.getTypeAttribute()));
			}
		}
		
		if (!texts.isEmpty()) {
			for (HtmlTextInput i : texts) {
				SecurityLoggers.inputDiscoveryLogger.log(Level.INFO, 
						String.format("Text field discovered on page (%s): [id=%s, name=%s, type=%s]", 
								form.getPage().getUrl(), i.getId(), i.getNameAttribute(), i.getTypeAttribute()));
			}
		}
		
		if (form.getByXPath("//input[@type='password']").size() == 1) {
			final HtmlTextInput userNameField = texts.get(0);
			final HtmlPasswordInput passwordField = passwords.get(0);
			if (SecurityFuzzer.authentication) {
				userNameField.setValueAttribute(SecurityFuzzer.userName);
				passwordField.setValueAttribute(SecurityFuzzer.password);

				// Now submit the form by clicking the button and get back the second page.
				try {
					SecurityFuzzer.requestWait();
					final HtmlPage page = submitButton.click();
					if (page.getWebResponse().getStatusCode() == Constants.UNAUTHORIZED_STATUS_CODE){
						SecurityLoggers.errorLogger.log(Level.SEVERE, "Bad credentials passed in");
						System.exit(0);
					}
				} catch (IOException e) {
					SecurityLoggers.errorLogger.log(Level.SEVERE, "Exception in clicking submit button", e);
				}
			} else {
				if (SecurityFuzzer.passwordGuess == true) {
					FuzzInput.tryCommonLogin(userNameField, passwordField, submitButton, form);
				}
				FuzzInput.fuzzAuthentication(userNameField, passwordField, submitButton, form);
			}
		} else {
			Set<DomElement> otherElements = Completeness.getDomElementCompletenessSet(form);
			
			if (!otherElements.isEmpty()) {
				for (DomElement i : otherElements) {
					SecurityLoggers.inputDiscoveryLogger.log(Level.INFO, 
							String.format("Input field discovered on page (%s): [id=%s, name=%s, type=%s]", 
									form.getPage().getUrl(), i.getAttribute("id"), 
									i.getAttribute("name"), i.getAttribute("type")));
				}
			}
			
			FuzzInput.fuzzMultiPassword(passwords.get(0), passwords.get(1), submitButton, otherElements, form);
		}
		
	}
	
	public static void parseQueryStrings(String url){
		
		URL myURL;
		try {
			myURL = new URL(url);
			
			String query = myURL.getQuery();
			if(query != null){
				String[] params = query.split("&"); 
				if(params.length > 1) {
					List<String> queries = new ArrayList<String>();
					for (String param : params) { 
						
						String queryToAdd = param.split("=")[0] + "=";
						if(SecurityFuzzer.isSensitive){
							for(String value: SecurityFuzzer.sensitiveData){
								if(param.split("=")[1]==value){
									SecurityLoggers.sensitiveDataLogger.log(Level.INFO, 
											String.format("Sensitive data discovered [%s] on page (%s): [%s]", value,url, queryToAdd));
								}
							}
						}
						SecurityFuzzer.queryStrings.add(queryToAdd);
						queries.add(queryToAdd);
						//SecurityLoggers.queryStringLogger.log(Level.INFO, 
							//	String.format("Query discovered on page (%s): [%s]", url, queryToAdd));
					}
					FuzzInput.fuzzQueryStrings(queries,url);
				} else {
					List<String> queries = new ArrayList<String>();
					String queryToAdd = query.split("=")[0];
					if(SecurityFuzzer.isSensitive){
						for(String value: SecurityFuzzer.sensitiveData){

							String temp =query.split("=")[1];
							if(temp.equals(value)){
								SecurityLoggers.sensitiveDataLogger.log(Level.INFO, 
										String.format("Sensitive data discovered [%s] on page (%s): [%s]", value,url, queryToAdd));
							}
						}
					}
					queries.add(queryToAdd + "=");
					FuzzInput.fuzzQueryStrings(queries,url);
					//SecurityLoggers.queryStringLogger.log(Level.INFO, 
						//	String.format("Query discovered on page (%s): [%s]", url, queryToAdd));
					//SecurityFuzzer.queryStrings.add(queryToAdd + "=");
				}
			}
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
		
	}
	
}
